﻿using System;
using System.Collections.Generic;
using System.Linq;
using KaroEngine;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace KaroGame.FrontEnd
{
    public class TileList : ITileProvider
    {
        #region Properties
        private KaroGame game;
        private Model tileModel;
        private Dictionary<int, KaroTile> tiles;

        private int hovered;
        private int selected;
        
        public KaroTile this[int id]
        {
            get { return tiles[id]; }
            set { tiles[id] = value; }
        }

        public Dictionary<int, KaroTile> Tiles
        {
            get { return tiles; }
        }

        public Dictionary<int, KaroTile> GhostTiles
        {
            get
            {
                Dictionary<int, KaroTile> ghostTiles = new Dictionary<int, KaroTile>();
                foreach (KeyValuePair<int, KaroTile> kvp in tiles)
                {
                    if (kvp.Value.Ghost)
                        ghostTiles.Add(kvp.Key, kvp.Value);
                }
                return ghostTiles;
            }
        }

        public int Hovered
        {
            get { return hovered; }
        }

        public int Selected
        {
            get { return selected; }
        }
        #endregion

        #region Constructors
        public TileList(KaroGame game)
        {
            this.game = game;
            this.tiles = new Dictionary<int, KaroTile>();
            this.selected = -1;
            this.hovered = -1;
        }
        #endregion

        #region Public Methods
        public void LoadContent()
        {
            this.tileModel = game.Content.Load<Model>("Tile");
        }

        public void NewGame()
        {
            ( (IConeProvider) game.Services.GetService( typeof(IConeProvider) ) ).Clear();

            foreach (KaroTile tile in tiles.Values)
            {
                game.Components.Remove( tile );
            }

            tiles = new Dictionary<int, KaroTile>();

            KaroTile newTile;
            int id = 154;
            float halfBoardWidth = game.BoardWidth / 2f;
            float halfBoardHeight = game.BoardHeight / 2f;
            for (float y = halfBoardHeight + 0.5f; y > -halfBoardHeight + 0.5f; y--)
            {
                for (float x = -halfBoardWidth + 0.5f; x < halfBoardWidth + 0.5f; x++)
                {
                    newTile = new KaroTile( game, tileModel, id, x, y - 1 );
                    game.Components.Add( newTile );
                    tiles.Add( id, newTile );

                    id++;
                }

                id += Engine.width - (int)game.BoardWidth;
            }

            CalculateGhostTiles();
        }

        public void CalculateGhostTiles()
        {
            Dictionary<int, KaroTile> tempList = new Dictionary<int, KaroTile>();
            KaroTile newTile;
            Vector2 coordinates;

            foreach (KaroTile t in GhostTiles.Values)
                tiles.Remove(t.Id);

            foreach (KaroTile t in tiles.Values)
            {
                // can left be ghosted
                if (!tiles.ContainsKey(t.Id - 1) && !tempList.ContainsKey(t.Id - 1))
                {
                    coordinates = t.GetLeftTileCoordinates();
                    newTile = new KaroTile(game, tileModel, t.Id - 1, coordinates.X, coordinates.Y);
                    newTile.Ghost = true;
                    game.Components.Add(newTile);
                    tempList.Add(t.Id - 1, newTile);
                }

                // can right be ghosted
                if (!tiles.ContainsKey(t.Id + 1) && !tempList.ContainsKey(t.Id + 1))
                {
                    coordinates = t.GetRightTileCoordinates();
                    newTile = new KaroTile(game, tileModel, t.Id + 1, coordinates.X, coordinates.Y);
                    newTile.Ghost = true;
                    game.Components.Add(newTile);
                    tempList.Add(t.Id + 1, newTile);
                }

                // can top be ghosted
                if (!tiles.ContainsKey(t.Id - 21) && !tempList.ContainsKey(t.Id - 21))
                {
                    coordinates = t.GetTopTileCoordinates();
                    newTile = new KaroTile(game, tileModel, t.Id - 21, coordinates.X, coordinates.Y);
                    newTile.Ghost = true;
                    game.Components.Add(newTile);
                    if(!tempList.ContainsKey(t.Id - 21))
                        tempList.Add(t.Id - 21, newTile);
                }
                
                // can bottom be ghosted
                if (!tiles.ContainsKey(t.Id + 21) && !tempList.ContainsKey(t.Id + 21))
                {
                    coordinates = t.GetBottomTileCoordinates();
                    newTile = new KaroTile(game, tileModel, t.Id + 21, coordinates.X, coordinates.Y);
                    newTile.Ghost = true;
                    game.Components.Add(newTile);
                    tempList.Add(t.Id + 21, newTile);
                }
            }

            foreach (KeyValuePair<int, KaroTile> kvp in tempList)
                tiles.Add(kvp.Key, kvp.Value);
        }

        public KaroTile GetById(int tileId)
        {
            return tiles[tileId];
        }

        public void Move(int tileIdFrom, int tileIdTo)
        {
            // TODO: Start an animation when moving a tile

            tiles[tileIdFrom].Ghost = true;
            tiles[tileIdTo].Ghost = false;
            CalculateGhostTiles();

            // Doesn't work yet, comment if it crashes here
            #region Setting Zoom Bounds
            float minX = float.MaxValue;
            float minY = float.MaxValue;
            float maxX = float.MinValue;
            float maxY = float.MinValue;
            foreach ( KaroTile tile in tiles.Values )
            {
                minX = minX < tile.X ? minX : tile.X;
                minY = minY < tile.Y ? minY : tile.Y;
                maxX = maxX > tile.X ? maxX : tile.X;
                maxY = maxY > tile.Y ? maxY : tile.Y;
            }

            float addedWidth = (Math.Abs( minX ) + Math.Abs( maxX )) - game.BoardWidth;
            float addedHeight = (Math.Abs( minY ) + Math.Abs( maxY )) - game.BoardHeight;
            float maxDimension = Math.Max( addedWidth, addedHeight );
            float zoomBound = ( (0.15f * (float)Math.Pow(0.97f, maxDimension)) * maxDimension );

            game.NearZoomBound = 0.6f - zoomBound;
            game.FarZoomBound = -0.75f - zoomBound;

            game.CentreScreen = new Vector3( -(minX + maxX) / 2, -(minY + maxY) / 2, 0 );
            #endregion
        }

        public int Select(Ray mouseRay, ref float distance)
        {
            Dictionary<int, float> collisions = new Dictionary<int, float>();

            foreach (KaroTile tile in tiles.Values)
            {
                tile.Deactivate();

                float? tempDistance = tile.Intersects(mouseRay);
                if (tempDistance != null)
                {
                    collisions.Add(tile.Id, tempDistance.Value);
                }
            }

            if (collisions.Count > 0)
            {
                KeyValuePair<int, float> minimumDistance = collisions.First();

                foreach (KeyValuePair<int, float> keyValuePair in collisions)
                {
                    if (keyValuePair.Value < minimumDistance.Value)
                    {
                        minimumDistance = keyValuePair;
                    }
                }

                distance = minimumDistance.Value;

                return minimumDistance.Key;
            }

            distance = float.NaN;

            return -1;
        }

        public void Activate(int tileId)
        {
            if (tiles.ContainsKey(tileId))
            {
                tiles[tileId].Activate();
                hovered = tileId;
            }
        }

        public void Deactivate()
        {
            foreach(KaroTile t in tiles.Values)
                t.Deactivate();
            hovered = -1;
        }

        public void Select(int tileId)
        {
            if (tiles.ContainsKey(tileId))
            {
                tiles[tileId].Select();
                selected = tileId;
            }
        }

        public void Deselect()
        {
            foreach (KaroTile t in tiles.Values)
                t.Deselect();
            selected = -1;
        }
        #endregion
    }
}